package com.auth.token.base;

import com.alibaba.fastjson.JSON;
import com.auth.dao.entity.Token;
import com.auth.dao.mapper.TokenMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Optional;

/**
 * @author: 王霄
 * @date: 2022/1/31 21:33
 */
public class TokenMysqlClient<T> implements TokenClient<T> {

    private final TokenParams tokenParams;
    private final TokenGenerator tokenGenerator;
    private final TokenMapper tokenMapper;
    private final Class<T> tClass;

    public TokenMysqlClient(TokenParams tokenParams,
                            TokenGenerator tokenGenerator,
                            TokenMapper tokenMapper,
                            Class<T> tClass) {
        this.tokenParams = tokenParams;
        this.tokenGenerator = tokenGenerator;
        this.tokenMapper = tokenMapper;
        this.tClass = tClass;
    }

    @Override
    public TokenGroup askToken(T data) {
        String accessToken = tokenGenerator.generate();
        String refreshToken = tokenGenerator.generate();
        LocalDateTime now = LocalDateTime.now();
        Token token = new Token()
                .setAccessToken(accessToken)
                .setRefreshToken(refreshToken)
                .setAtBeginTime(now)
                .setRtBeginTime(now)
                .setData(JSON.toJSONString(data))
                .setCreateTime(now);
        int result = tokenMapper.insert(token);
        if (result > 0) {
            return TokenGroup.builder()
                    .accessToken(accessToken)
                    .refreshToken(refreshToken)
                    .accessTokenExp(Instant.now().plusSeconds(tokenParams.getAccessTokenExpiration()))
                    .build();
        }
        return null;
    }

    @Override
    public TokenGroup refreshToken(String refreshToken) {
        LocalDateTime now = LocalDateTime.now();
        LambdaQueryWrapper<Token> queryWrapper = new LambdaQueryWrapper<Token>()
                .eq(Token::getRefreshToken, refreshToken);
        Token token = tokenMapper.selectOne(queryWrapper);
        if (token != null) {
            LocalDateTime rtExpTime = token.getRtBeginTime()
                    .plus(tokenParams.getRefreshTokenExpiration(), ChronoUnit.MILLIS);
            if (rtExpTime.isAfter(now)) {
                String accessToken = tokenGenerator.generate();
                token.setAccessToken(accessToken);
                token.setAtBeginTime(now);
                int result = tokenMapper.updateById(token);
                if (result > 0) {
                    return TokenGroup.builder()
                            .accessToken(accessToken)
                            .refreshToken(refreshToken)
                            .accessTokenExp(Instant.now().plusSeconds(tokenParams.getAccessTokenExpiration()))
                            .build();
                }
            }
        }
        return null;
    }

    @Override
    public T getData(String accessToken) {
        LambdaQueryWrapper<Token> queryWrapper = new LambdaQueryWrapper<Token>()
                .eq(Token::getAccessToken, accessToken);
        Token token = tokenMapper.selectOne(queryWrapper);
        return Optional.ofNullable(token)
                .filter(t -> atIsExpired(t.getAtBeginTime()))
                .map(Token::getData)
                .map(data -> JSON.parseObject(data, tClass))
                .orElse(null);
    }

    @Override
    public void delByAccessToken(String accessToken) {
        LambdaQueryWrapper<Token> queryWrapper = new LambdaQueryWrapper<Token>()
                .eq(Token::getAccessToken, accessToken);
        Token token = tokenMapper.selectOne(queryWrapper);
        Optional.ofNullable(token)
                .filter(t -> atIsExpired(t.getAtBeginTime()))
                .ifPresent(tokenMapper::deleteById);
    }

    @Override
    public void delByRefreshToken(String refreshToken) {
        LambdaQueryWrapper<Token> queryWrapper = new LambdaQueryWrapper<Token>()
                .eq(Token::getRefreshToken, refreshToken);
        Token token = tokenMapper.selectOne(queryWrapper);
        Optional.ofNullable(token)
                .filter(t -> rtIsExpired(t.getRtBeginTime()))
                .ifPresent(tokenMapper::deleteById);
    }

    private boolean atIsExpired(LocalDateTime atBeginTime) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime atExpTime = atBeginTime.plus(tokenParams.getAccessTokenExpiration(), ChronoUnit.MILLIS);
        return atExpTime.isAfter(now);
    }

    private boolean rtIsExpired(LocalDateTime rtBeginTime) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime rtExpTime = rtBeginTime.plus(tokenParams.getRefreshTokenExpiration(), ChronoUnit.MILLIS);
        return rtExpTime.isAfter(now);
    }
}
